package eval

import (
	"showen/lex"
	"showen/obj"
	"showen/parse"
	"testing"
)

func TestEvalLetComprehensive(t *testing.T) {
	tests := []struct {
		input string
		want  []string
	}{
		// Simple single variable binding
		{
			"(let ((x 1)) x)",
			[]string{"1"},
		},
		// Multiple variable bindings
		{
			"(let ((x 1) (y 2)) (+ x y))",
			[]string{"3"},
		},
		// Variable binding with expression
		{
			"(let ((x (+ 1 2))) (* x x))",
			[]string{"9"},
		},
		// Multiple expressions in body
		{
			"(let ((x 2) (y 3)) (define z 5) (+ x y z))",
			[]string{"10"},
		},
		// Nested let expressions
		{
			"(let ((x 1)) (let ((y 2)) (+ x y)))",
			[]string{"3"},
		},
		// Shadowing outer scope variables
		{
			"(let ((x 10)) (let ((x 20)) x))",
			[]string{"20"},
		},
		// Complex expressions in bindings
		{
			"(let ((x (* 2 3)) (y (+ 1 4))) (- x y))",
			[]string{"1"},
		},
		// Boolean expressions
		{
			"(let ((a #t) (b #f)) (and a b))",
			[]string{"#f"},
		},
		{
			"(let ((a #t) (b #f)) (or a b))",
			[]string{"#t"},
		},
		// String expressions
		{
			"(let ((str \"hello\")) (string-length str))",
			[]string{"5"},
		},
		// Using defined functions
		{
			"(define (square x) (* x x)) (let ((a 3) (b 4)) (+ (square a) (square b)))",
			[]string{"", "25"},
		},
		// Empty let (no bindings)
		{
			"(let () 42)",
			[]string{"42"},
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		exprs, err := parser.ParseProgram()
		if err != nil {
			t.Fatalf("parser.ParseProgram() error = %v", err)
		}

		for i, expr := range exprs {
			var o obj.Object
			o, err = EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("Input: %s, EvalExpression() error = %v", tt.input, err)
			}

			if o.Inspect() != tt.want[i] {
				t.Errorf("Input: %s, Expression %d: EvalExpression() = %v, want %v", tt.input, i, o.Inspect(), tt.want[i])
			}
		}
	}
}

func TestEvalLetErrorCases(t *testing.T) {
	errorTests := []struct {
		input         string
		expectedError string
	}{
		{
			"(let)",
			"let requires at least bindings and one body expression",
		},
		{
			"(let ())",
			"let requires at least bindings and one body expression",
		},
		{
			"(let ((x 1)))",
			"let requires at least bindings and one body expression",
		},
		{
			"(let x 1)",
			"let bindings must be a list",
		},
		{
			"(let ((x)) x)",
			"let binding must have exactly two elements: variable and value",
		},
		{
			"(let ((x 1 2)) x)",
			"let binding must have exactly two elements: variable and value",
		},
		{
			"(let ((1 2)) 1)",
			"let variable must be an identifier",
		},
		{
			"(let ((x undefined-var)) x)",
			"identifier not found: undefined-var",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range errorTests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		exprs, err := parser.ParseProgram()
		if err != nil {
			t.Fatalf("parser.ParseProgram() error = %v", err)
		}

		// Evaluate all expressions
		for _, expr := range exprs {
			_, err = EvalExpression(expr, env)
			if err == nil {
				t.Errorf("Input: %s, expected error but got none", tt.input)
				continue
			}

			if err.Error() != tt.expectedError {
				t.Errorf("Input: %s, expected error %q, got %q", tt.input, tt.expectedError, err.Error())
			}
		}
	}
}

func TestEvalLetStarComprehensive(t *testing.T) {
	tests := []struct {
		input string
		want  []string
	}{
		// Simple single variable binding
		{
			"(let* ((x 1)) x)",
			[]string{"1"},
		},
		// Multiple variable bindings - each can reference previous ones
		{
			"(let* ((x 1) (y (+ x 1))) (+ x y))",
			[]string{"3"},
		},
		// Sequential binding with complex expressions
		{
			"(let* ((x (* 2 3)) (y (+ x 1)) (z (* y 2))) (+ x y z))",
			[]string{"27"},
		},
		// Variable binding with expression
		{
			"(let* ((x (+ 1 2))) (* x x))",
			[]string{"9"},
		},
		// Multiple expressions in body
		{
			"(let* ((x 2) (y 3)) (define z 5) (+ x y z))",
			[]string{"10"},
		},
		// Nested let* expressions
		{
			"(let* ((x 1)) (let* ((y 2)) (+ x y)))",
			[]string{"3"},
		},
		// Shadowing outer scope variables
		{
			"(let* ((x 10)) (let* ((x 20)) x))",
			[]string{"20"},
		},
		// Complex expressions in bindings
		{
			"(let* ((x (* 2 3)) (y (+ x 4))) (- y x))",
			[]string{"4"},
		},
		// Boolean expressions
		{
			"(let* ((a #t) (b #f)) (and a b))",
			[]string{"#f"},
		},
		{
			"(let* ((a #t) (b (not a))) (or a b))",
			[]string{"#t"},
		},
		// String expressions
		{
			"(let* ((str \"hello\") (len (string-length str))) len)",
			[]string{"5"},
		},
		// Using defined functions
		{
			"(define (square x) (* x x)) (let* ((a 3) (b (square a))) (+ a b))",
			[]string{"", "12"},
		},
		// Empty let* (no bindings)
		{
			"(let* () 42)",
			[]string{"42"},
		},
		// Deep sequential dependency
		{
			"(let* ((x 1) (y (* x 2)) (z (* y 3)) (w (* z 4))) w)",
			[]string{"24"},
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		exprs, err := parser.ParseProgram()
		if err != nil {
			t.Fatalf("parser.ParseProgram() error = %v", err)
		}

		for i, expr := range exprs {
			var o obj.Object
			o, err = EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("Input: %s, EvalExpression() error = %v", tt.input, err)
			}

			if o.Inspect() != tt.want[i] {
				t.Errorf("Input: %s, Expression %d: EvalExpression() = %v, want %v", tt.input, i, o.Inspect(), tt.want[i])
			}
		}
	}
}

func TestEvalLetStarErrorCases(t *testing.T) {
	errorTests := []struct {
		input         string
		expectedError string
	}{
		{
			"(let*)",
			"let* requires at least bindings and one body expression",
		},
		{
			"(let* ())",
			"let* requires at least bindings and one body expression",
		},
		{
			"(let* ((x 1)))",
			"let* requires at least bindings and one body expression",
		},
		{
			"(let* x 1)",
			"let* bindings must be a list",
		},
		{
			"(let* ((x)) x)",
			"let* binding must have exactly two elements: variable and value",
		},
		{
			"(let* ((x 1 2)) x)",
			"let* binding must have exactly two elements: variable and value",
		},
		{
			"(let* ((1 2)) 1)",
			"let* variable must be an identifier",
		},
		{
			"(let* ((x undefined-var)) x)",
			"identifier not found: undefined-var",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range errorTests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		exprs, err := parser.ParseProgram()
		if err != nil {
			t.Fatalf("parser.ParseProgram() error = %v", err)
		}

		// Evaluate all expressions
		for _, expr := range exprs {
			_, err = EvalExpression(expr, env)
			if err == nil {
				t.Errorf("Input: %s, expected error but got none", tt.input)
				continue
			}

			if err.Error() != tt.expectedError {
				t.Errorf("Input: %s, expected error %q, got %q", tt.input, tt.expectedError, err.Error())
			}
		}
	}
}
